package file

import (
	"errors"
	"fmt"
	"github.com/Sir-yuChen/funAnalyse/global"
	"github.com/Sir-yuChen/funAnalyse/global/constant"
	"github.com/Sir-yuChen/funAnalyse/model"
	"github.com/Sir-yuChen/funAnalyse/model/common/ApiReturn"
	"github.com/Sir-yuChen/funAnalyse/model/request"
	"github.com/Sir-yuChen/funAnalyse/model/resp"
	"github.com/Sir-yuChen/funAnalyse/service/commonService"
	"github.com/Sir-yuChen/funAnalyse/utils"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
	"path"
	"reflect"
	"strings"
	"time"
)

type StockFilesService struct{}

func NewStockFilesService() *StockFilesService {
	return &StockFilesService{}
}

//GetStockFiles 多条件查询
func (u *StockFilesService) GetStockFiles(req *model.StockFilesModel) (a ApiReturn.ApiReturnCode) {
	var mo []model.StockFilesModel
	sql := ""
	obj := model.NewStockFilesModel()
	tableName := obj.TableName()
	sqlCondition := utils.ReflectHandleStuct(req)
	if sqlCondition != "" {
		sql = `select * from ` + tableName + ` where ` + sqlCondition
	} else {
		sql = `select * from ` + tableName + ` order by id desc  limit 100 `
	}
	err := global.GVA_DB.Raw(sql).Scan(&mo).Error
	if err != nil {
		global.GVA_LOG.Error("GetStockFiles异常", zap.Any("request", req), zap.Any("sql", sql), zap.Error(err))
		return ApiReturn.ErrSystem
	}
	s := ApiReturn.OK
	s.Data = mo
	return s
}

//UpdateStockFiles 更新
func (u *StockFilesService) UpdateStockFiles(req *model.StockFilesModel) (a ApiReturn.ApiReturnCode) {
	if req.Id == 0 {
		return ApiReturn.ErrParam
	}
	err := global.GVA_DB.Model(model.StockFilesModel{}).Updates(req).Error
	if err != nil {
		global.GVA_LOG.Error("UpdateStockFiles异常", zap.Any("request", req), zap.Error(err))
		return ApiReturn.ErrSystem
	}
	return ApiReturn.OK
}

//DelStockFiles 根据ID删除
func (u *StockFilesService) DelStockFiles(req *model.StockFilesModel) (a ApiReturn.ApiReturnCode) {
	if req.Id == 0 {
		return ApiReturn.ErrParam
	}
	err := global.GVA_DB.Model(model.StockFilesModel{}).Where("id = ?", req.Id).Delete(model.StockFilesModel{}).Error
	if err != nil {
		global.GVA_LOG.Error("DelStockFiles异常", zap.Any("request", req), zap.Error(err))
		return ApiReturn.ErrSystem
	}
	return ApiReturn.OK
}

func (u *StockFilesService) UploadFiles(req *request.UploadFileRequest, c *gin.Context) (a ApiReturn.ApiReturnCode) {
	forms, err := c.MultipartForm()
	if err != nil {
		global.GVA_LOG.Info("上传文件失败", zap.Error(err))
		return ApiReturn.UploadFailed
	}
	files := forms.File["files"]
	global.GVA_LOG.Info("上传文件", zap.Any("request", req), zap.Any("files", len(files)))
	return UpLoadMultipartFiles(files, req, c)
}
func (u *StockFilesService) DownloadFile(req *request.DownloadFileRequest, c *gin.Context) ApiReturn.ApiReturnCode {
	var mo model.StockFilesModel
	//校验文件记录和状态
	err := global.GVA_DB.Model(model.StockFilesModel{}).Where(" file_name = ? And file_type = ?  ", req.FileName, req.FileType).First(&mo).Error
	if err != nil && err.Error() != constant.ERROR_NO_DATA {
		global.GVA_LOG.Error("校验文件记录异常", zap.Any("request", req), zap.Error(err))
		return ApiReturn.ErrSystem
	}
	if mo == (model.StockFilesModel{}) {
		global.GVA_LOG.Error("文件记录不存在", zap.Any("request", req))
		return ApiReturn.NoRecordFiles
	}
	if mo.Status != constant.STATUS_Y {
		return ApiReturn.FileDisabled
	}
	//校验通过 开始下载
	err = handleDownload(c, &mo)
	if err != nil {
		failed := ApiReturn.DownloadFailed
		failed.Msg = err.Error()
		return failed
	}
	return ApiReturn.OK
}

// UpLoadMultipartFiles  多文件上传
func UpLoadMultipartFiles(files []*multipart.FileHeader, req *request.UploadFileRequest, c *gin.Context) ApiReturn.ApiReturnCode {
	userInfo, err := commonService.GetUserInfoByToken(c)
	if err != nil {
		global.GVA_LOG.Error("获取操作账户信息异常", zap.Error(err))
		return ApiReturn.ErrSystem
	}
	var response []resp.UplocadeFilesResponse
	var uploadFaileds []string
	if len(files) < 1 {
		global.GVA_LOG.Error("上传文件 文件不能为空")
		return ApiReturn.NoFiles
	}
	for _, file := range files {
		//上传的文件名
		uid, _ := uuid.NewUUID()
		file.Filename = req.FileName + "_" + strings.Replace(uid.String(), "-", "", -1) + "_" + file.Filename
		var filesResponse resp.UplocadeFilesResponse
		err, host, fileName, pathRelative := actionUplaodFile(c, file)
		if err != nil {
			uploadFaileds = append(uploadFaileds, file.Filename)
			global.GVA_LOG.Error("上传文件失败", zap.Any("file", file), zap.Error(err))
			if len(files) == len(uploadFaileds) {
				global.GVA_LOG.Error("所有文件上传失败", zap.Any("files", files))
				return ApiReturn.UploadFailed
			}
			continue
		}
		var rItem request.FileItem
		rItem.Host = host
		rItem.Uri = pathRelative
		rItem.Url = path.Join(host, pathRelative)
		//mysql 记录
		extString := strings.ToUpper(Ext(file.Filename))
		i := strings.LastIndex(file.Filename, ".")
		i2 := strings.Index(extString, ".")

		now := model.LocalTime(time.Now())
		fileModel := model.StockFilesModel{
			FileName:    string(fileName[:i]),
			FileType:    req.FileType, //暂未使用
			FilePath:    rItem.Url,
			Status:      constant.STATUS_Y,
			FileEffect:  req.FileEffect,
			FileSuffix:  string(extString[i2+1:]),
			Drive:       global.GVA_CONFIG.File.Drive,
			CreateBy:    userInfo.CustomerName,
			CreateTime:  &now,
			LastUpdTime: &now,
		}
		if err := global.GVA_DB.Model(model.StockFilesModel{}).Create(&fileModel).Error; err != nil {
			global.GVA_LOG.Error("上传文件,表记录异常", zap.Any("fileModel", fileModel), zap.Error(err))
		}
		filesResponse.FileName = string(fileName[:i])
		filesResponse.FileId = fileModel.Id
		filesResponse.FilePath = rItem.Url
		response = append(response, filesResponse)
	}
	ok := ApiReturn.OK
	ok.Data = response
	return ok
}

func actionUplaodFile(c *gin.Context, file *multipart.FileHeader) (err error, host, filename, pathRelative string) {
	// 判断上传文件的大小
	if global.GVA_CONFIG.File.FileMaxSize != 0 {
		fsize := file.Size //上传文件的大小
		if fsize > global.GVA_CONFIG.File.FileMaxSize*1024*1024 {
			global.GVA_LOG.Error("文件大小不符合要求", zap.Any("FileMaxSize", global.GVA_CONFIG.File.FileMaxSize))
			return errors.New("文件大小不符合要求"), "", "", ""
		}
	}
	//获取上传文件后缀
	extString := strings.ToUpper(Ext(file.Filename))
	if extString == "" {
		global.GVA_LOG.Error("上传失败,文件类型不支持,获取文件后缀为空", zap.Any("UpdateVilidateExtString", global.GVA_CONFIG.File.UpdateVilidateExtString))
		return errors.New("未获取到正确的文件后缀"), "", "", ""
	}
	//验证文件类型
	if len(global.GVA_CONFIG.File.UpdateVilidateExtString) > 0 {
		VilidateExtStrSliceTmp := strings.Split(strings.ToUpper(global.GVA_CONFIG.File.UpdateVilidateExtString), ",")
		VilidateExtStrSlice := make([]string, 0)
		for _, v := range VilidateExtStrSliceTmp {
			VilidateExtStrSlice = append(VilidateExtStrSlice, fmt.Sprintf(".%s", v))
		}
		//验证文件类型
		//extString .JPG
		//VilidateExtStrSlice  []string{".JPG",".JPEG",".PNG",".GIF",".BMP"}
		if !ContainArray(extString, VilidateExtStrSlice) {
			global.GVA_LOG.Error("上传失败，文件类型不支持", zap.Any("UpdateVilidateExtString", global.GVA_CONFIG.File.UpdateVilidateExtString))
			return errors.New("文件类型不支持"), "", "", ""
		}
	}
	switch global.GVA_CONFIG.File.Drive {
	case constant.FILE_TYPE_DRIVE_LOCAL:
		return uploadfileToLocal(c, file, file.Filename)
	case constant.FILE_TYPE_DRIVE_ALI_OSS:
		return uploadfileToOss(c, file, file.Filename)
	default:
		global.GVA_LOG.Error("只支持上传到本地,阿里oss")
		return
	}
}

//上传到本地
func uploadfileToLocal(c *gin.Context, file *multipart.FileHeader, filename string) (err error, host, name, pathRelative string) {
	host = global.GVA_CONFIG.File.UploadConfLocal.Host
	filepath := path.Join(global.GVA_CONFIG.File.UploadConfLocal.LocalDirectory, time.Now().Format("20060102"))
	//上传到的路径
	pathRelative = path.Join(filepath, filename) //路径+文件名上传
	//如果没有filepath文件目录就创建一个
	if _, err := os.Stat(filepath); err != nil {
		if !os.IsExist(err) {
			os.MkdirAll(filepath, os.ModePerm)
		}
	}
	// 上传文件到指定的目录
	err = c.SaveUploadedFile(file, pathRelative)
	if err != nil {
		global.GVA_LOG.Error("本地上传文件失败", zap.Any("filename", filename), zap.Error(err))
		return err, host, filename, ""
	}
	return nil, host, filename, pathRelative
}

//先上传到本地再上传到OSS
func uploadfileToOss(c *gin.Context, file *multipart.FileHeader, filename string) (err error, host, name, ossPathFileName string) {
	host = global.GVA_CONFIG.File.AliyunOss.Host
	directory := global.GVA_CONFIG.File.AliyunOss.Directory
	err, _, filename, localPathFileName := uploadfileToLocal(c, file, filename)
	if err != nil {
		global.GVA_LOG.Error("本地上传临时目录失败", zap.Any("filename", filename), zap.Error(err))
		return err, host, filename, ""
	}
	ossPath := path.Join(directory, time.Now().Format("20060102"))
	ossPathFileName = path.Join(ossPath, file.Filename)
	// 创建OSSClient实例。
	client, err := oss.New(global.GVA_CONFIG.File.AliyunOss.Endpoint, global.GVA_CONFIG.File.AliyunOss.AccessKeyId, global.GVA_CONFIG.File.AliyunOss.AccessKeySecret)
	if err != nil {
		os.Remove(localPathFileName)
		global.GVA_LOG.Error("文件上传服务器失败 创建OSSClient实例", zap.Error(err))
		return err, host, filename, ""
	}
	// 获取存储空间。
	bucket, err := client.Bucket(global.GVA_CONFIG.File.AliyunOss.BucketName)
	if err != nil {
		os.Remove(localPathFileName)
		global.GVA_LOG.Error("文件上传云端失败 获取存储空间", zap.Error(err))
		return err, host, filename, ""
	}
	// 上传文件。
	err = bucket.PutObjectFromFile(ossPathFileName, localPathFileName)
	if err != nil {
		os.Remove(localPathFileName)
		global.GVA_LOG.Error("文件上传云端失败 上传文件", zap.Error(err))
		return err, host, filename, ""
	}
	err = os.Remove(localPathFileName) //如果本地不想删除,可以注释了
	if err != nil {
		global.GVA_LOG.Error("文件上传云端 删除本地缓存失败", zap.Error(err))
	}
	return err, host, filename, ossPathFileName
}

func handleDownload(c *gin.Context, req *model.StockFilesModel) error {
	if req.Drive == constant.FILE_TYPE_DRIVE_LOCAL {
		//本地校验文件是否存在
		exist, err := utils.FileExist(req.FilePath)
		if err != nil {
			global.GVA_LOG.Error("下载文件 校验文件是否存在异常", zap.Error(err))
			return errors.New(ApiReturn.DownloadFailed.Msg)
		}
		if exist != true {
			// 文件不存在 修改数据库状态
			if err := global.GVA_DB.Model(model.StockFilesModel{}).Where(" id = ?", req.Id).Update("status", "D").Error; err != nil {
				global.GVA_LOG.Error("下载文件 文件不存在，修改文件状态异常", zap.Error(err))
				return errors.New(ApiReturn.ErrSystem.Msg)
			}
			return errors.New(ApiReturn.NoFaile.Msg)
		}
	}
	//获取文件名称带后缀
	fileNameWithSuffix := path.Base(req.FilePath)
	//获取文件的后缀
	fileType := path.Ext(fileNameWithSuffix)
	//获取文件类型对应的http ContentType 类型
	fileContentType := HttpContentType[fileType]
	c.Header("Content-Type", fileContentType)
	//强制浏览器下载
	c.Header("Content-Disposition", "attachment; url="+req.FilePath)
	//浏览器下载或预览
	c.Header("Content-Disposition", "attachment; fileName="+req.FileName+"."+strings.ToLower(req.FileSuffix))
	c.Header("Content-Transfer-Encoding", "binary")
	c.Header("Cache-Control", "no-cache")
	c.Header("Content-Length", "-1")
	c.Header("Transfer-Encoding", "chunked")
	c.File(req.FilePath)
	fileByt, _ := ioutil.ReadFile(req.FilePath)
	c.Data(http.StatusOK, fileContentType, fileByt)
	return nil
}

// Ext 获取文件的扩展名
func Ext(path string) string {
	for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
		if path[i] == '.' {
			return path[i:]
		}
	}
	return ""
}

// ContainArray Contain 判断obj是否在target中，target支持的类型array,slice,map   false:不在 true:在
func ContainArray(obj interface{}, target interface{}) bool {
	targetValue := reflect.ValueOf(target)
	switch reflect.TypeOf(target).Kind() {
	case reflect.Slice, reflect.Array:
		for i := 0; i < targetValue.Len(); i++ {
			if targetValue.Index(i).Interface() == obj {
				return true
			}
		}
	case reflect.Map:
		if targetValue.MapIndex(reflect.ValueOf(obj)).IsValid() {
			return true
		}
	}

	return false
}
